#include "CCubeSceneNode.h"
#include "CProgramVPS.h"
#include "CShaderManager.h"
#include "CSceneManager.h"
#include "CRenderDriverD3D11.h"
#include "CLightsManager.h"
#include "DDefaultShaders.h"
#include "SMeshBuffer.h"
#include "SAnimatedMesh.h"
#include "SMesh.h"
#include "SDeviceMap.h"
#include "CBufferObject.h"
#include "CLightsManager.h"
#include "DDefaultShaders.h"
#include "ILDXException.h"
#include "CMeshCache.h"

ldx::scene::CCubeSceneNode::CCubeSceneNode(ISceneNode * parent, CSceneManager * mgr, int id, const mt::vec3f & position, const mt::vec3f & rotation, const mt::vec3f & scale)
	:ISceneNode(parent,mgr,id,position,rotation,scale), 
	ProgramName(_DEFAULT_PROGRAM_PHONG_STD),
	CubeName("cube_mesh_std"),
	AnimatedMesh(nullptr)
{
#ifdef _DEBUG
	setName("CCubeSceneNode");
#endif
	Material = render::IdentityMaterial;
	if (!mgr)
	{
		std::cout << "error! CCubeSceneNode: mgr == nullptr!" << std::endl;
		return;
	}
	Driver = static_cast<render::CRenderDriverD3D11*>(mgr->getRenderDriver());
	LightsMgr = SceneManager->getLightsManager();
	render::CShaderManager* ShaderMgr = Driver->getShaderManager();
	Program = static_cast<render::CProgramVPS*>(ShaderMgr->getProgram(ProgramName));
	if (Program)
		Program->grab();
	else
		throw os::ILDXException(L"CubeSceneNode Program == nullptr!");
	DM = Driver->getDeviceMap();

	if(SceneManager->getMeshCache()->isMeshLoaded(CubeName))
		AnimatedMesh = SceneManager->getMesh(CubeName);
	if (!AnimatedMesh)//if first time create cube.
	{
#pragma region data
		const 	float vertexData[] =
		{
			//  X     Y     Z       U     V          Normal
			// bottom
			-1.0f,-1.0f,-1.0f,   0.0f, 1.0f,   0.0f, -1.0f, 0.0f,
			1.0f,-1.0f,-1.0f,   1.0f, 1.0f,   0.0f, -1.0f, 0.0f,
			1.0f,-1.0f, 1.0f,   1.0f, 0.0f,   0.0f, -1.0f, 0.0f,
			-1.0f,-1.0f, -1.0f,   0.0f, 1.0f,   0.0f, -1.0f, 0.0f,
			1.0f,-1.0f, 1.0f,   1.0f, 0.0f,   0.0f, -1.0f, 0.0f,
			-1.0f,-1.0f, 1.0f,   0.0f, .0f,   0.0f, -1.0f, 0.0f,

			// top
			-1.0f, 1.0f,-1.0f,   0.0f, 0.0f,   0.0f, 1.0f, 0.0f,
			-1.0f, 1.0f, 1.0f,   0.0f, 1.0f,   0.0f, 1.0f, 0.0f,
			1.0f, 1.0f, 1.0f,   1.0f, 1.0f,   0.0f, 1.0f, 0.0f,
			-1.0f, 1.0f,-1.0f,   0.0f, 0.0f,   0.0f, 1.0f, 0.0f,
			1.0f, 1.0f, 1.0f,   1.0f, 1.0f,   0.0f, 1.0f, 0.0f,
			1.0f, 1.0f, -1.0f,   1.0f, 0.0f,   0.0f, 1.0f, 0.0f,

			// front
			-1.0f,-1.0f, -1.0f,   0.0f, 0.0f,   0.0f, 0.0f,-1.0f,
			-1.0f,1.0f, -1.0f,   1.0f, 0.0f,   0.0f, 0.0f,-1.0f,
			1.0f, 1.0f, -1.0f,   1.0f, 1.0f,   0.0f, 0.0f,-1.0f,
			-1.0f,-1.0f, -1.0f,   0.0f, 0.0f,   0.0f, 0.0f,-1.0f,
			1.0f, 1.0f, -1.0f,   1.0f, 1.0f,   0.0f, 0.0f,-1.0f,
			1.0f, -1.0f, -1.0f,   1.0f, 0.0f,   0.0f, 0.0f,-1.0f,

			// back
			1.0f,-1.0f,1.0f,   0.0f, 0.0f,   0.0f, 0.0f, 1.0f,
			1.0f, 1.0f,1.0f,   0.0f, 1.0f,   0.0f, 0.0f, 1.0f,
			-1.0f,1.0f,1.0f,   1.0f, 1.0f,   0.0f, 0.0f, 1.0f,
			1.0f,-1.0f,1.0f,   0.0f, 0.0f,   0.0f, 0.0f, 1.0f,
			-1.0f, 1.0f,1.0f,   1.0f, 1.0f,   0.0f, 0.0f, 1.0f,
			-1.0f, -1.0f,1.0f,   1.0f, 0.0f,   0.0f, 0.0f, 1.0f,

			// left
			-1.0f,-1.0f, -1.0f,   0.0f, 0.0f,   -1.0f, 0.0f, 0.0f,
			-1.0f, -1.0f,1.0f,   .0f, 1.0f,   -1.0f, 0.0f, 0.0f,
			-1.0f, 1.0f, 1.0f,   1.0f, 1.0f,   -1.0f, 0.0f, 0.0f,
			-1.0f,-1.0f, -1.0f,   0.0f, 0.0f,   -1.0f, 0.0f, 0.0f,
			-1.0f, 1.0f, 1.0f,   1.0f, 1.0f,   -1.0f, 0.0f, 0.0f,
			-1.0f, 1.0f,-1.0f,   1.0f, 0.0f,   -1.0f, 0.0f, 0.0f,

			// right
			1.0f,-1.0f,-1.0f,   0.0f, 0.0f,   1.0f, 0.0f, 0.0f,
			1.0f, 1.0f,-1.0f,   0.0f, 1.0f,   1.0f, 0.0f, 0.0f,
			1.0f, 1.0f, 1.0f,   1.0f, 1.0f,   1.0f, 0.0f, 0.0f,
			1.0f,-1.0f,-1.0f,   0.0f, 0.0f,   1.0f, 0.0f, 0.0f,
			1.0f, 1.0f, 1.0f,   1.0f, 1.0f,   1.0f, 0.0f, 0.0f,
			1.0f,-1.0f, 1.0f,   1.0f, 0.0f,   1.0f, 0.0f, 0.0f
		};
#pragma endregion
		SMeshBuffer* Buffer = new SMeshBuffer();
		for (int i = 0; i < 36; ++i)
		{
			render::S3DVertex tempV;
			tempV.Pos = mt::vec3f(vertexData[i * 8 + 0], vertexData[i * 8 + 1], vertexData[i * 8 + 2]);
			tempV.TCoords = mt::vector2df(vertexData[i * 8 + 3], vertexData[i * 8 + 4]);
			tempV.Normal = mt::vec3f(vertexData[i * 8 + 5], vertexData[i * 8 + 6], vertexData[i * 8 + 7]);
			Buffer->Vertices.push_back(tempV);
			Buffer->Indices.push_back(i);
		}
		Buffer->recalculateBoundingBox();
		//Buffer->Material = render::IdentityMaterial;//don't use this material.

		//Material.setFlag(render::EMF_LIGHTING, false);

		SceneManager->addMesh(CubeName, Buffer);
		Buffer->initHardwareBuffers(Driver->getDeviceMap());//put buffer onto GPU and get the buffer object.
		Buffer->drop();
		AnimatedMesh = SceneManager->getMesh(CubeName);
		AnimatedMesh->grab();
	}
	initLocalBuffers();
}

ldx::scene::CCubeSceneNode::~CCubeSceneNode()
{
	if (Program)
		Program->drop();
	if(AnimatedMesh)
		AnimatedMesh->drop();
	deleteLocalBuffers();
}

void ldx::scene::CCubeSceneNode::OnRegisterSceneNode()
{
	if (IsVisible)
	{
		SceneManager->registerNodeForRendering(this, ESNRP_SOLID);
	}

	ISceneNode::OnRegisterSceneNode();
}

void ldx::scene::CCubeSceneNode::render()
{
	SDeviceMap dm = Driver->getDeviceMap();
	// set program
	Program->use();
	// set buffers
	// camera data
	SceneManager->setGlobalConstantBuffers(render::EST_VERTEXSHADER | render::EST_PIXELSHADER, 0, 0);
	// dynamic data
	//lights ps::cb1
	if (Material.Lighting)
	{
		LightsMgr->setCurrentLights(getAbsolutePosition(), Driver->getViewPosition());
		LightsMgr->setLightsConstantBuffers(render::EST_PIXELSHADER, 0, 1);
	}
	// material
	updateLocalBuffers();
	bindLocalBuffers();
	//set textures
	//if(Material.isTextured())
	{
		for (mt::uint i = 0; i < render::E_TEXTURE_LAYER_TYPE::ETLT_COUNT;++i)
		{
			if (Material.IsLayerUsed[i])
			{
				Material.TextureLayer[i].use(DM, render::EST_PIXELSHADER, 0, true, true);
			}
		}
	}
	//now drawing
	IMeshBuffer* mb = AnimatedMesh->getMesh(0)->getMeshBuffer(0);
	mb->use();
	//DM.Window.D3DDeviceContext->Draw
	DM.Window.D3DDeviceContext->DrawIndexed(mb->getIndexCount(), 0, 0);
	//DM.Window.D3DDeviceContext->Draw(mb->getVertexCount(), 0);
	Program->stopUsing();
}

void ldx::scene::CCubeSceneNode::update(float deltaTime)
{
	ISceneNode::update(deltaTime);
}

ldx::render::SMaterial & ldx::scene::CCubeSceneNode::getMaterial(uint num)
{
	return Material;
}

void ldx::scene::CCubeSceneNode::setMaterial(render::SMaterial material)
{
	Material = material;
	updateLocalBuffers();
}

void ldx::scene::CCubeSceneNode::setTexture(std::string filename, int index)
{
	//"media//textures//wooden-crate.jpg"
	render::CTexture* tex = Driver->addTexture(filename);
	if (tex)
	{
		Material.IsLayerUsed[index] = true;
		Material.TextureLayer[index].Texture = tex;
		Material.TextureLayer[index].initSampler(DM);
	}
}

void ldx::scene::CCubeSceneNode::initLocalBuffers()
{
	BufferMaterialObj = new render::CBufferObject(DM, D3D11_USAGE_DEFAULT,
		sizeof(render::CBufferMaterial), D3D11_BIND_CONSTANT_BUFFER,0, 0);
	BufferModelObj = new render::CBufferObject(DM, D3D11_USAGE_DEFAULT, sizeof(render::CBufferModel),
		D3D11_BIND_CONSTANT_BUFFER, 0, 0);
	BufferTransposInverseModelObj = new render::CBufferObject(DM, D3D11_USAGE_DEFAULT, sizeof(render::CBufferModel),
		D3D11_BIND_CONSTANT_BUFFER, 0, 0);
	BufferMaterialObj->setDebugName("cube_obj_material_buffer");
	BufferModelObj->setDebugName("cube_obj_model_buffer");
	BufferTransposInverseModelObj->setDebugName("cube_obj_transposinversemodel_buffer");
	updateLocalBuffers();
}

void ldx::scene::CCubeSceneNode::updateLocalBuffers()
{
	mt::mat4 model = getAbsoluteTransformation();
	BufferModel.Model = DirectX::XMMatrixTranspose(model.getXMMatrix());
	BufferMaterial.AmbientColor = mt::vec4f(Material.AmbientColor.R,
		Material.AmbientColor.G,
		Material.AmbientColor.B,
		Material.AmbientColor.A);
	BufferMaterial.DiffuseColor = mt::vec4f(Material.DiffuseColor.R,
		Material.DiffuseColor.G,
		Material.DiffuseColor.B,
		Material.DiffuseColor.A);
	BufferMaterial.EmissiveColor = mt::vec4f(Material.EmissiveColor.R,
		Material.EmissiveColor.G,
		Material.EmissiveColor.B,
		Material.EmissiveColor.A);
	BufferMaterial.SpecularColor = mt::vec4f(Material.SpecularColor.R,
		Material.SpecularColor.G,
		Material.SpecularColor.B,
		Material.SpecularColor.A);
	Material.setFlags(BufferMaterial.MaterialFlags.x, BufferMaterial.MaterialFlags.y,
		BufferMaterial.MaterialFlags.z);
	BufferMaterial.Shininess = Material.Shininess;

	mt::mat4 inverse;
	if (!model.getInverse(inverse))
		std::cout << "error inverse!" << std::endl;
	//inverse  = inverse.getTransposed();
	BufferTransposInverseModel.Model = inverse.getXMMatrix();// DirectX::XMMatrixTranspose(inverse.getXMMatrix());

	// update gpu buffer
	BufferMaterialObj->updateSubBuffer(&BufferMaterial);
	BufferModelObj->updateSubBuffer(&BufferModel);
	BufferTransposInverseModelObj->updateSubBuffer(&BufferTransposInverseModel);
}

void ldx::scene::CCubeSceneNode::deleteLocalBuffers()
{
	if (BufferMaterialObj)
		delete BufferMaterialObj;
	if (BufferModelObj)
		delete BufferModelObj;
}

void ldx::scene::CCubeSceneNode::bindLocalBuffers()
{
	//vs
	//model cb1
	DM.Window.D3DDeviceContext->VSSetConstantBuffers(1, 1, BufferModelObj->getBufferObject());
	//ps
	//material cb2
	DM.Window.D3DDeviceContext->PSSetConstantBuffers(2, 1, BufferMaterialObj->getBufferObject());
	DM.Window.D3DDeviceContext->PSSetConstantBuffers(3, 1, BufferModelObj->getBufferObject());
	DM.Window.D3DDeviceContext->PSSetConstantBuffers(4, 1, BufferTransposInverseModelObj->getBufferObject());
}
